package physics.component;

import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics2D;
import java.awt.geom.Point2D;
import physics.Force;
import physics.Simulation;


/**
 * Mass Object that is Attached at Each End to a Spring.
 *
 * @author Robert C. Duvall, minorly edited by Kevin Gao and Connor Gordon
 */
public class Mass extends PhysicalObject {

    // Cardinal Directions
    /**
     * Angle that points right.
     */
    public static final int RIGHT = 0;
    /**
     * Angle that points down.
     */
    public static final int DOWN = 90;
    /**
     * Angle that points left.
     */
    public static final int LEFT = 180;
    /**
     * Angle that points up.
     */
    public static final int UP = 270;

    private static final int DIAMETER = 16;
    private static final int BOUNCE_MAGNITUDE = 2;

    // state
    private Point2D myCenter;
    private Force myVelocity;
    private final Dimension mySize;
    private final int myID;
    private final double myMass;
    private final Force myAcceleration;

    /**
     * @param id identification number
     * @param x initial x position
     * @param y initial y position
     * @param mass value of mass in grams
     */
    public Mass (final int id, final double x,
            final double y, final double mass) {
        myAcceleration = new Force();
        myMass = mass;
        myID = id;
        setCenter(x, y);
        setVelocity(0, 0);
        mySize = new Dimension(DIAMETER, DIAMETER);
    }

    @Override
    public final void drawShape (final Graphics2D pen) {
        pen.fillOval(getLeft(), getTop(), getSize().width, getSize().height);
    }

    @Override
    protected void determineColor (final Graphics2D pen) {
        pen.setColor(Color.BLACK);
    }

    /**
     * Returns the distance between the centers of two masses.
     *
     * @param m Mass to get distance to
     * @return distance in pixels between the two masses
     */
    public double getDistanceBetween (Mass m) {
        return Math.sqrt(Math.pow(myCenter.getX() - m.getCenter().getX(), 2)
                + Math.pow(myCenter.getY() - m.getCenter().getY(), 2));
    }

    @Override
    /**
     * Calculates Net Force acting on Mass object and moves proportionally
     * @param canvas screen area which Simulation takes place
     * @param dt change in time since last call
     */
    public void update (final Simulation canvas, final double dt) {
        applyForce(getBounce(canvas.getSize()));
        // convert force back into Mover's velocity
        getVelocity().sum(myAcceleration);
        myAcceleration.reset();
        // move mass by velocity
        myCenter.setLocation(myCenter.getX() + myVelocity.getXChange() * dt,
                myCenter.getY() + myVelocity.getYChange() * dt);
    }

    /**
     * @param f
     *        force to be applied to this mass
     */
    public final void applyForce (final Force f) {
        myAcceleration.sum(f);
    }

    /**
     * Returns true this object's ID matches a user-input id.
     *
     * @param id the identification number of the mass object
     * @return whether or not the id's match
     */
    public final boolean match (final int id) {
        return myID == id;
    }

    /**
     * Check for move out of bounds.
     *
     * @param bounds The dimension of the containing canvas
     * @return An impulse from bouncing off the wall
     */
    private Force getBounce (final Dimension bounds) {
        Force impulse = new Force();
        if (getLeft() < 0) {
            impulse = new Force(RIGHT, BOUNCE_MAGNITUDE);
            setCenter(getSize().width / 2, getCenter().getY());
        }
        else if (getRight() > bounds.width) {
            impulse = new Force(LEFT, BOUNCE_MAGNITUDE);
            setCenter(bounds.width - getSize().width / 2, getCenter().getY());
        }
        if (getTop() < 0) {
            impulse = new Force(UP, BOUNCE_MAGNITUDE);
            setCenter(getCenter().getX(), getSize().height / 2);
        }
        else if (getBottom() > bounds.height) {
            impulse = new Force(DOWN, BOUNCE_MAGNITUDE);
            setCenter(getCenter().getX(), bounds.height - getSize().height / 2);
        }
        impulse.scale(getVelocity().getRelativeMagnitude(impulse));
        return impulse;
    }

    /**
     * Returns shape's velocity.
     *
     * @return The shape's velocity
     */
    public final Force getVelocity () {
        return myVelocity;
    }

    /**
     * Returns shape's acceleration.
     *
     * @return acceleration of mass
     */
    public final Force getAcceleration () {
        return myAcceleration;
    }

    /**
     * Resets shape's velocity.
     *
     * @param direction Angle of velocity vector
     * @param magnitude Mangitude of velocity vector
     */
    public final void setVelocity (final double direction,
            final double magnitude) {
        myVelocity = new Force(direction, magnitude);
    }

    /**
     * Returns shape's center.
     *
     * @return Center of the shape
     */
    public final Point2D getCenter () {
        return myCenter;
    }

    /**
     * Resets shape's center.
     *
     * @param x new center x coordinate
     * @param y new center y coordinate
     */
    public final void setCenter (final double x, final double y) {
        myCenter = new Point2D.Double(x, y);
    }

    /**
     * Returns shape's left-most coordinate.
     *
     * @return Left-most coordinate
     */
    public final int getLeft () {
        return (int) (getCenter().getX() - getSize().width / 2);
    }

    /**
     * Returns shape's top-most coordinate.
     *
     * @return Top-most coordinate
     */
    public final int getTop () {
        return (int) (getCenter().getY() - getSize().height / 2);
    }

    /**
     * Returns shape's right-most coordinate.
     *
     * @return Right-most coordinate
     */
    public final int getRight () {
        return (int) (getCenter().getX() + getSize().width / 2);
    }

    /**
     * Reports shape's bottom-most coordinate.
     *
     * @return bottom-most coordinate
     */
    public final int getBottom () {
        return (int) (getCenter().getY() + getSize().height / 2);
    }

    /**
     * Returns shape's size.
     *
     * @return Dimensions of shape
     */
    public final Dimension getSize () {
        return mySize;
    }

    /**
     * Returns object's mass.
     *
     * @return Mass of object
     */
    public final double getMass () {
        return myMass;
    }

    /**
     * Returns whether or not a physical object is a mass.
     *
     * @param po PhysicalObject in question
     * @return true if mass, false if not mass
     */
    public static final boolean isMass (PhysicalObject po) {
        return po instanceof Mass;
    }

}
